package cn.dansj.controller;

import cn.dansj.bean.*;
import cn.dansj.common.request.annotation.encrypt.EncryptResponseDataAspect;
import cn.dansj.common.request.annotation.http.RequestMappingMethod;
import cn.dansj.common.utils.bean.RequestToken;
import cn.dansj.entity.InterfaceInfo;
import cn.dansj.entity.User;
import cn.dansj.enums.GlobalEnv;
import cn.dansj.common.utils.json.DictMap;
import cn.dansj.common.utils.enums.ResponseCode;
import cn.dansj.exception.InterfaceResponseException;
import cn.dansj.common.request.RequestUtils;
import cn.dansj.common.utils.http.UrlUtils;
import cn.dansj.common.redis.lock.RedisLock;
import cn.dansj.common.redis.RedisUtils;
import cn.dansj.common.utils.transfer.*;
import cn.dansj.utils.interfaces.InterfaceUtils;
import cn.dansj.utils.project.ProjectUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.script.RedisScript;

import java.sql.Timestamp;
import java.util.*;

@RequestMappingMethod
public class InterfaceRequestController {
    private final Logger logger = LoggerFactory.getLogger(InterfaceRequestController.class);

    private final RedisUtils redisUtils;
    private final RedisScript<Long> limitScript;
    private final RedisScript<Long> countScript;

    public InterfaceRequestController(RedisUtils redisUtils, @Qualifier("limitScript") RedisScript<Long> limitScript, @Qualifier("countScript") RedisScript<Long> countScript) {
        this.redisUtils = redisUtils;
        this.limitScript = limitScript;
        this.countScript = countScript;
    }

    @RequestMappingMethod(InterfaceConstant.InterfaceBaseRequestURI)
    public Object getMethodsHandler() {
        HttpServletRequest request = RequestUtils.getRequest();
        boolean withProjectToken = Transformation.castToBoolean(request.getAttribute(GlobalEnv.WithProjectToken));

        // 判断是否返回debug信息
        boolean debug = Transformation.lambdaTryCatch(() -> {
            final RequestToken requestToken = ProjectUtils.parseRequestToken();
            return "console".equals(requestToken.getPlatform()) && Transformation.patternExist(requestToken.getCurrentPath(), "/interface/(api|detail)$") && withProjectToken;
        }, () -> false);

        //获取请求controller的路径
        Object interfaceUrl = RequestUtils.fromDispatcher();
        String url = Transformation.nvl(interfaceUrl, request.getRequestURI(), "").toString();

        JSONObject params = RequestUtils.getRequestParams();
        String useragent = request.getHeader("user-agent");
        String method = request.getMethod().toLowerCase();
        InterfaceCheckReturn interfaceCheckReturn = InterfaceUtils.checkGetInterfaceInfo(url, method);
        InterfaceResponse interfaceResponse = interfaceCheckReturn.getInterfaceResponse();
        if (interfaceResponse.getResponseCode() == 404) {
            return interfaceResponse;
        }

        InterfaceInfo interfaceInfo = interfaceCheckReturn.getInterfaceInfo();

        //状态非发布状态的全部返回404
        if (interfaceInfo.getStatus() != 1) {
            interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.NotExist);
            return interfaceResponse;
        }

        //记录所有访问接口的次数
        GlobalEnv.threadPool.execute(() -> {
            redisUtils.getRedisTemplate().execute(countScript, Collections.singletonList(GlobalEnv.InterfaceTotalCountPrefix), "incr");
            redisUtils.getRedisTemplate().execute(countScript, Collections.singletonList(GlobalEnv.InterfaceTotalCountPrefix + "@" + interfaceInfo.getSequence()), "incr");
        });

        final String host = UrlUtils.getHost(Transformation.nvl(request.getHeader("Origin"), request.getHeader("Referer"), ""));
        String realIp = RequestUtils.getRealIp();
        // 黑名单校验
        List<String> blackList = ArrayUtils.asList(StringUtils.split(interfaceInfo.getBlackList(), ","));
        if (blackList.contains(host) || blackList.contains(realIp)) {
            return RequestUtils.response(ResponseCode.NoAuthorize);
        }

        //校验接口权限  需要验证接口权限的,检查是否需要校验限流
        final int apiType = interfaceInfo.getApiType();
        request.setAttribute("apiType", apiType);
        if (interfaceInfo.getCheckPermission() == 1) {
            List<String> whiteList = ArrayUtils.asList(StringUtils.split(ProjectUtils.getSettingByRedis("intercept", "interface.domain.white.list", ""), ","), StringUtils.split(interfaceInfo.getWhiteList(), ","));
            final boolean internalRequest = RequestUtils.isInternalRequest();
            final boolean inWhiteList = whiteList.contains(host) || whiteList.contains(realIp) || internalRequest;
            // 内部接口必须使用加密token请求
            if (apiType == 1 && !withProjectToken && !inWhiteList) {
                return RequestUtils.response(ResponseCode.NoAuthorize);
            }

            try {
                // 校验token的有效性
                ProjectUtils.checkUserInterfaceTokenInfo();
                // 判断token的策略
                boolean distributeToken = checkDistributeTokenAndLimit();
                if (!withProjectToken && !distributeToken && !inWhiteList) {
                    return RequestUtils.response(ResponseCode.NoAuthorize);
                }
            } catch (InterfaceResponseException exception) {
                interfaceResponse.setStatus(false).setMessage(exception.getMessage()).setResponseCode(exception.getErrorCode());
                return RequestUtils.response(exception.getErrorCode(), exception.getMessage());
            } catch (Exception exception) {
                logger.error(exception.getMessage(), exception);
                return RequestUtils.response(ResponseCode.Error);
            }
        }

        //----------------------------------------------------------------------------------------------------------------------------------------------------------------------
        //----------------------------------------------------------------所有的后续操作在此之后----------------------------------------------------------------------------------
        //----------------------------------------------------------------------------------------------------------------------------------------------------------------------
        int useCache = interfaceInfo.getUseCache();
        boolean encryptData = Transformation.castToBoolean(interfaceInfo.getEncrypt());
        String token = Transformation.nvl(Transformation.toString(request.getAttribute("x-token")), request.getHeader("token"), RequestUtils.getRequestParams().getString("token"), "");
        String key = String.format("InterfaceResult@%s", Md5Utils.md5(url + method + params.toJSONString() + token));

        if (useCache > 0 || useCache == -1) {
            Map<Object, Object> cacheResult = redisUtils.hgetall(key);
            if (Verification.checkNotNull(cacheResult)) {
                Date cacheCrtTime = GetTime.timestamp(cacheResult.getOrDefault("timestamp", "").toString());
                Timestamp interfaceUpdateTime = interfaceInfo.getUpdateTime();
                int compare = interfaceUpdateTime.compareTo(cacheCrtTime);

                if (compare < 0) {
                    try {
                        final Object cacheObject = cacheResult.get("cache");
                        if (cacheObject != null) {
                            Object cacheResp = JSON.isValidObject(cacheObject.toString()) ? JSONObject.parseObject(DictMap.parseObject(cacheObject, JSONObject.class).toJSONString(), Class.forName(cacheResult.get("class").toString())) : cacheObject;

                            if (cacheResp instanceof InterfaceResponse) {
                                if (!debug) ((InterfaceResponse) cacheResp).resetSourceCode("0");
                                ((InterfaceResponse) cacheResp).setResponseEndTime(DateTime.getInstance().stringTimestamp());
                            }
                            incrUserTokenCount();
                            return encryptData ? EncryptResponseDataAspect.transferData(cacheResp, "data", Object.class) : cacheResp;
                        }
                    } catch (Exception ignore) {

                    }
                }
            }
        }

        //接口请求单线程锁
        Object obj;
        if (interfaceInfo.getRequestLock() == 1) {
            try (RedisLock ignored = new RedisLock("interfaceLock@" + key, 60000, 60000)) {
                obj = InterfaceUtils.getInstance().exec();
            }
        } else {
            obj = InterfaceUtils.getInstance().exec();
        }

        if (useCache > 0 || useCache == -1) {
            Map<String, Object> cacheMap = new HashMap<>();
            cacheMap.put("cache", Transformation.toJSONString(obj));
            cacheMap.put("params", params.toJSONString());
            cacheMap.put("url", url);
            cacheMap.put("timestamp", DateTime.getInstance().stringTimestamp());
            cacheMap.put("class", obj.getClass().getName());
            redisUtils.hmset(key, cacheMap, useCache);
        }

        if (obj instanceof InterfaceResponse && !debug) ((InterfaceResponse) obj).resetSourceCode("1");
        incrUserTokenCount();
        return encryptData ? EncryptResponseDataAspect.transferData(obj, "data", Object.class) : obj;
    }

    //校验单独的token信息
    private boolean checkDistributeTokenAndLimit() throws InterfaceResponseException {
        User user = CurrentThreadLocalUserInfo.getCurrentThreadLocalUserInfo().getTokenUser();
        boolean checkPass = Verification.checkNotNull(user.getSequence()) && Transformation.castToBoolean(user.getStatus());

        //限流
        if (checkPass) {
            boolean vip = ProjectUtils.checkInterfaceVip(user.getRole());
            if (vip) {
                return true;
            }

            //根据当前积分计算等级以及限流情况
            LimitReturn limitReturn = ProjectUtils.getLimit(user.getPoint());
            int time = limitReturn.getTime();
            int count = limitReturn.getCount();
            int total = limitReturn.getTotal();
            int lv = limitReturn.getLv();

            //获取指定时间内的访问次数
            Long number = redisUtils.getRedisTemplate().execute(limitScript, Collections.singletonList("limit@" + user.getToken()), time, count);
            if (number.intValue() > count) {
                //超过限流阈值
                throw new InterfaceResponseException(ResponseCode.TooMany.getStatusCode(), String.format(ResponseCode.TooMany.getError(), lv, count));
            }

            //获取当天的访问次数
            long useCount = getUserTokenCount();
            if (useCount > total) {
                throw new InterfaceResponseException(ResponseCode.UpperLimit.getStatusCode(), String.format(ResponseCode.UpperLimit.getError(), lv, total));
            }
        }

        return checkPass;
    }

    private String getCurrentUser() {
        return Transformation.nvl(CurrentThreadLocalUserInfo.getCurrentThreadLocalUserInfo().getTokenUser().getToken(), CurrentThreadLocalUserInfo.getCurrentThreadLocalUserInfo().getCurrentUser().getSequence());
    }

    private long getUserTokenCount() {
        //获取当天的访问次数
        return redisUtils.getRedisTemplate().execute(countScript, Collections.singletonList("count@" + getCurrentUser()), "get");
    }

    private void incrUserTokenCount() {
        long expire = DateTime.getInstance().endOfDay().toDate().getTime() - System.currentTimeMillis();
        //获取当天的访问次数
        redisUtils.getRedisTemplate().execute(countScript, Collections.singletonList("count@" + getCurrentUser()), "incr", expire / 1000);
    }
}
